package com.auicyh.tylb.utils;

import android.util.Base64;

import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * 作者：  Ocean 2017/12/29 15:39
 * 功能：  加解密工具类
 * Q Q：   1126067113
 * 修改：
 * @author Ocean
 */

public class EnCodeUtils {

    /** 密钥长度 AES 支持 128、192 和 256 比特长度的密钥,这里使用128 */
    private static final int KEY_LENGTH = 128;
    /**
     * 盐长度 盐值的字节数组长度，注意这里是字节数组的长度,其长度值需要和最终输出的密钥字节数组长度一致
     * 由于这里密钥的长度是 256 比特，则最终密钥将以
     * 256/8 = 32 位长度的字节数组存在 所以盐值的字节数组长度也应该是 32
     * saltLength = keyLength / 8;
     * getSaltStr() 获取盐  128 16 byte数组  转成 hex 32位
     */
    private static final String STATE = getSaltStr(16);


    /**
     * 加密
     * @param str   要加密的数据
     * @param state 盐32
     * @param key   key
     * @return 返回加密后的字符串
     * @throws TylbException 加密异常
     */
    public static String enCode(String str, String state, String key) throws TylbException {
        return enCode(str, state, key, "");
    }


    /**
     * 加密
     * @param str   要加密的数据
     * @param state 盐 32
     * @param key   key
     * @param iv    iv向量 16字符串
     * @return 返回加密后的字符串
     * @throws TylbException 加密异常
     */
    public static String enCode(String str, String state, String key, String iv) throws TylbException {
        if (StringUtils.isStrNull(str)) {
            return "";
        }
        // 向量数组长度16位
        byte[] bytes;
        if (StringUtils.isStrNull(iv)) {
            // iv未Null取state
            bytes = DataConversion.hexToByteArr(state);
        } else {
            bytes = iv.getBytes();
        }
        if (bytes == null || bytes.length != 16) {
            throw new TylbException("向量数组出问题", 709);
        }
        try {
            // 获取密钥数组
            SecretKeySpec keySpec = getSecretKey(key, state, KEY_LENGTH);
            // 设置向量  IvParameterSpec数组长度不小于12位
            IvParameterSpec ivParameterSpec = new IvParameterSpec(bytes);
            // 创建加密器
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            // 加密模式 IvParameterSpec
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParameterSpec);
            // base64编码
            byte[] ret = Base64.encode(cipher.doFinal(str.getBytes()), Base64.DEFAULT);
            return new String(ret, "UTF-8");
        } catch (NoSuchAlgorithmException e) {
            throw new TylbException("加密异常NoSuchAlgorithmException", 701);
        } catch (NoSuchPaddingException e) {
            throw new TylbException("加密异常NoSuchPaddingException", 702);
        } catch (InvalidKeyException e) {
            throw new TylbException("加密异常InvalidKeyException", 703);
        } catch (InvalidAlgorithmParameterException e) {
            throw new TylbException("加密异常InvalidAlgorithmParameterException", 704);
        } catch (IllegalBlockSizeException e) {
            throw new TylbException("加密异常IllegalBlockSizeException", 705);
        } catch (BadPaddingException e) {
            throw new TylbException("加密异常BadPaddingException", 706);
        } catch (UnsupportedEncodingException e) {
            throw new TylbException("加密异常UnsupportedEncodingException", 707);
        } catch (InvalidKeySpecException e) {
            throw new TylbException("加密异常InvalidKeySpecException", 708);
        }
    }

    /**
     * @param str   要界面的字符串
     * @param state 盐
     * @param key   key
     * @return 解密的数据
     */
    public static String deCode(String str, String state, String key) {
        return deCode(str, state, key, "");
    }

    /**
     * 解密
     * @param str   要解密的数据
     * @param state 盐
     * @param key   key
     * @return 解密的数据
     */
    public static String deCode(String str, String state, String key, String iv) {
        try {
            if (StringUtils.isStrNull(str)) {
                return "";
            }

            // 向量数组长度16位
            byte[] bytes;
            if (StringUtils.isStrNull(iv)) {
                // iv为Null取state
                bytes = DataConversion.hexToByteArr(state);
            } else {
                bytes = iv.getBytes();
            }
            if (bytes == null || bytes.length != 16) {
                throw new TylbException("向量数组出问题", 709);
            }

            // 转换为Aei专用密钥
            SecretKeySpec keySpec = getSecretKey(key, state, KEY_LENGTH);
            // 以使最终生成的密文产生差异
            IvParameterSpec ivParameterSpec = new IvParameterSpec(bytes);
            byte[] d = Base64.decode(str, Base64.DEFAULT);
            Cipher instance = Cipher.getInstance("AES/CBC/PKCS5Padding");
            instance.init(Cipher.DECRYPT_MODE, keySpec, ivParameterSpec);
            byte[] c = instance.doFinal(d);
            return new String(c, "UTF-8");
        } catch (Exception e) {
            return "";
        }
    }


    /**
     * 盐值的字节数组长度，注意这里是字节数组的长度,其长度值需要和最终输出的密钥字节数组长度一致
     * 由于这里密钥的长度是 256 比特，则最终密钥将以 256/8 = 32 位长度的字节数组存在 所以盐值的字节数组长度也应该是 32
     * saltLength = 32
     * <p>
     * 先获取一个随机的盐值 你需要将此次生成的盐值保存到磁盘上下次再从字符串换算密钥时传入
     * 如果盐值不一致将导致换算的密钥值不同, 保存密钥的逻辑官方并没写，需要自行实现
     * @param key       key
     * @param salt      盐 长度是 keyLength/8   可以是 16、24、32
     * @param keyLength key的长度，长度可以是128、192 和 256
     * @return SecretKeySpec SecretKeySpec可能为空需要判断
     * @throws NoSuchAlgorithmException NoSuchAlgorithmException
     * @throws InvalidKeySpecException  InvalidKeySpecException
     */
    public static SecretKeySpec getSecretKey(String key, String salt, int keyLength) throws NoSuchAlgorithmException, InvalidKeySpecException {
        // 将密码明文、盐值等使用新的方法换算密钥
        byte[] bytes = DataConversion.hexToByteArr(salt);
        KeySpec keySpec = new PBEKeySpec(key.toCharArray(), bytes, 50, keyLength);
        // PBKDF2WithHmacSHA1 一直比MD5更安全的一种算法
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        // 到这里你就能拿到一个安全的密钥了
        byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
        return new SecretKeySpec(keyBytes, "AES");
    }

    /**
     * @param saltLength 长度 keyLength/8   可以是 16、24、32
     *                   随机获取盐
     * @return 获取盐
     */
    public static String getSaltStr(int saltLength) {
        SecureRandom random = new SecureRandom();
        byte[] salt = new byte[saltLength];
        random.nextBytes(salt);
        return DataConversion.byteArrToHex(salt);
    }
}
